realtime.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import { useQueryClient } from '@tanstack/react-query'
  2. import { useParams } from 'common'
  3. import dayjs from 'dayjs'
  4. import { ArrowRight, RefreshCw } from 'lucide-react'
  5. import { useEffect, useMemo, useRef, useState } from 'react'
  6. import ReportFilterBar from '@/components/interfaces/Reports/ReportFilterBar'
  7. import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
  8. import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
  9. import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants'
  10. import ReportStickyNav from '@/components/interfaces/Reports/ReportStickyNav'
  11. import { SharedAPIReport } from '@/components/interfaces/Reports/SharedAPIReport/SharedAPIReport'
  12. import { useSharedAPIReport } from '@/components/interfaces/Reports/SharedAPIReport/SharedAPIReport.constants'
  13. import { ReportChartV2 } from '@/components/interfaces/Reports/v2/ReportChartV2'
  14. import { LogsDatePicker } from '@/components/interfaces/Settings/Logs/Logs.DatePickers'
  15. import UpgradePrompt from '@/components/interfaces/Settings/Logs/UpgradePrompt'
  16. import DefaultLayout from '@/components/layouts/DefaultLayout'
  17. import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout'
  18. import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
  19. import { ObservabilityLink } from '@/components/ui/ObservabilityLink'
  20. import { realtimeReports } from '@/data/reports/v2/realtime.config'
  21. import { useRefreshHandler, useReportDateRange } from '@/hooks/misc/useReportDateRange'
  22. import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'
  23. import type { NextPageWithLayout } from '@/types'
  24. const RealtimeReport: NextPageWithLayout = () => {
  25. return (
  26. <ReportPadding>
  27. <RealtimeUsage />
  28. </ReportPadding>
  29. )
  30. }
  31. RealtimeReport.getLayout = (page) => (
  32. <DefaultLayout>
  33. <ObservabilityLayout title="Realtime">{page}</ObservabilityLayout>
  34. </DefaultLayout>
  35. )
  36. export type UpdateDateRange = (from: string, to: string) => void
  37. export default RealtimeReport
  38. const RealtimeUsage = () => {
  39. const [isRefreshing, setIsRefreshing] = useState(false)
  40. const { db, chart, ref } = useParams()
  41. const {
  42. selectedDateRange,
  43. updateDateRange: updateDateRangeFromHook,
  44. datePickerValue,
  45. datePickerHelpers,
  46. showUpgradePrompt,
  47. setShowUpgradePrompt,
  48. handleDatePickerChange,
  49. } = useReportDateRange(REPORT_DATERANGE_HELPER_LABELS.LAST_60_MINUTES)
  50. const queryClient = useQueryClient()
  51. const {
  52. data,
  53. error,
  54. isLoading,
  55. refetch,
  56. isRefetching,
  57. filters,
  58. addFilter,
  59. removeFilters,
  60. isLoadingData,
  61. sql,
  62. } = useSharedAPIReport({
  63. filterBy: 'realtime',
  64. start: selectedDateRange?.period_start?.date,
  65. end: selectedDateRange?.period_end?.date,
  66. })
  67. const chartSyncId = `realtime-${ref}`
  68. const state = useDatabaseSelectorStateSnapshot()
  69. const reportConfig = useMemo(() => {
  70. return realtimeReports({
  71. projectRef: ref!,
  72. startDate: selectedDateRange?.period_start?.date ?? '',
  73. endDate: selectedDateRange?.period_end?.date ?? '',
  74. interval: selectedDateRange?.interval ?? 'minute',
  75. databaseIdentifier: state.selectedDatabaseId,
  76. })
  77. }, [ref, selectedDateRange, state.selectedDatabaseId])
  78. const onRefreshReport = useRefreshHandler(
  79. datePickerValue,
  80. datePickerHelpers,
  81. handleDatePickerChange,
  82. async () => {
  83. if (!selectedDateRange) return
  84. setIsRefreshing(true)
  85. queryClient.invalidateQueries({
  86. queryKey: ['projects', ref, 'report-v2', { queryGroup: 'realtime' }],
  87. })
  88. refetch()
  89. setTimeout(() => setIsRefreshing(false), 1000)
  90. }
  91. )
  92. const urlStateHasSyncedRef = useRef(false)
  93. useEffect(() => {
  94. if (urlStateHasSyncedRef.current) return
  95. urlStateHasSyncedRef.current = true
  96. if (db !== undefined) {
  97. setTimeout(() => {
  98. // [Joshen] Adding a timeout here to support navigation from settings to reports
  99. // Both are rendering different instances of ProjectLayout which is where the
  100. // DatabaseSelectorContextProvider lies in (unless we reckon shifting the provider up one more level is better)
  101. state.setSelectedDatabaseId(db)
  102. }, 100)
  103. }
  104. if (chart !== undefined) {
  105. setTimeout(() => {
  106. const el = document.getElementById(chart)
  107. if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' })
  108. }, 200)
  109. }
  110. }, [db, chart, state])
  111. const updateDateRange: UpdateDateRange = (from: string, to: string) => {
  112. updateDateRangeFromHook(from, to)
  113. }
  114. return (
  115. <>
  116. <ReportHeader showDatabaseSelector={false} title="Realtime" />
  117. <ReportStickyNav
  118. content={
  119. <div className="flex flex-col gap-3">
  120. <div className="flex gap-2">
  121. <ButtonTooltip
  122. type="default"
  123. disabled={isRefreshing}
  124. icon={<RefreshCw className={isRefreshing ? 'animate-spin' : ''} />}
  125. className="w-7"
  126. tooltip={{ content: { side: 'bottom', text: 'Refresh report' } }}
  127. onClick={onRefreshReport}
  128. />
  129. <LogsDatePicker
  130. onSubmit={handleDatePickerChange}
  131. value={datePickerValue}
  132. helpers={datePickerHelpers}
  133. />
  134. <UpgradePrompt
  135. show={showUpgradePrompt}
  136. setShowUpgradePrompt={setShowUpgradePrompt}
  137. title="Report date range"
  138. description="Report data can be stored for a maximum of 3 months depending on the plan that your project is on."
  139. source="realtimeReportDateRange"
  140. />
  141. {selectedDateRange && (
  142. <div className="flex items-center gap-x-2 text-xs">
  143. <p className="text-foreground-light">
  144. {dayjs(selectedDateRange.period_start.date).format('MMM D, h:mma')}
  145. </p>
  146. <p className="text-foreground-light">
  147. <ArrowRight size={12} />
  148. </p>
  149. <p className="text-foreground-light">
  150. {dayjs(selectedDateRange.period_end.date).format('MMM D, h:mma')}
  151. </p>
  152. </div>
  153. )}
  154. </div>
  155. </div>
  156. }
  157. >
  158. <div className="mt-8 grid md:grid-cols-2 gap-4">
  159. {selectedDateRange &&
  160. reportConfig
  161. .filter((report) => !report.hide)
  162. .map((report) => (
  163. <ReportChartV2
  164. key={`${report.id}`}
  165. queryGroup="realtime"
  166. report={report}
  167. projectRef={ref!}
  168. interval={selectedDateRange.interval}
  169. startDate={selectedDateRange?.period_start?.date}
  170. endDate={selectedDateRange?.period_end?.date}
  171. updateDateRange={updateDateRange}
  172. syncId={chartSyncId}
  173. />
  174. ))}
  175. </div>
  176. <div className="">
  177. <div className="mb-4">
  178. <h5 className="text-foreground mb-2">Realtime API Gateway</h5>
  179. <ReportFilterBar
  180. filters={filters}
  181. onAddFilter={addFilter}
  182. onRemoveFilters={removeFilters}
  183. isLoading={isLoadingData || isRefetching}
  184. hideDatepicker={true}
  185. datepickerHelpers={datePickerHelpers}
  186. selectedProduct={'realtime'}
  187. showDatabaseSelector={false}
  188. />
  189. </div>
  190. <SharedAPIReport
  191. data={data}
  192. error={error}
  193. isLoading={isLoading}
  194. isRefetching={isRefetching}
  195. hiddenReports={['networkTraffic']}
  196. sql={sql}
  197. />
  198. </div>
  199. </ReportStickyNav>
  200. <div className="py-8">
  201. <ObservabilityLink />
  202. </div>
  203. </>
  204. )
  205. }